package com.sakura.oj.sandbox.template.java;

import cn.hutool.core.io.FileUtil;
import cn.hutool.core.lang.UUID;
import cn.hutool.core.util.StrUtil;
import cn.hutool.dfa.WordTree;
import com.github.dockerjava.api.DockerClient;
import com.github.dockerjava.api.command.CreateContainerCmd;
import com.github.dockerjava.api.command.CreateContainerResponse;
import com.github.dockerjava.api.command.ExecCreateCmdResponse;
import com.github.dockerjava.api.model.Bind;
import com.github.dockerjava.api.model.Capability;
import com.github.dockerjava.api.model.HostConfig;
import com.github.dockerjava.api.model.Volume;
import com.sakura.oj.sandbox.constants.CommonConstant;
import com.sakura.oj.sandbox.constants.JudgeConstant;
import com.sakura.oj.sandbox.model.ExecuteMessage;
import com.sakura.oj.sandbox.template.AbstractCodeSandbox;
import com.sakura.oj.sandbox.utils.DockerExecuteCallback;
import com.sakura.oj.sandbox.utils.DockerMemoryUtils;
import com.sakura.oj.sandbox.utils.ProcessUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.util.StopWatch;
import org.springframework.util.StringUtils;

import javax.annotation.Resource;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.TimeUnit;

/**
 * Java语言的Docker沙箱实现
 */
@Slf4j
@Component
public class JavaDockerSandbox extends AbstractCodeSandbox {

    // 注入Docker客户端Bean
    @Resource
    private DockerClient dockerClient;

    // 注入DockerMemoryUtils Bean
    @Resource
    private DockerMemoryUtils dockerMemoryUtils;

    // 配置项
    private static final String DOCKER_IMAGE = "openjdk:17-slim";

    // 文件配置
    private static final String CODE_FILE_NAME = "Main.java";
    private static final String GLOBAL_CODE_PATH = CommonConstant.GLOBAL_CODE_DIR;
    private static final String[] COMPILE_CMD = {"javac", "-encoding", "utf-8"};
    private static final String[] RUN_CMD = {"java", "-cp", ".", "Main"};

    // 安全配置
    private static final List<String> BLACK_LIST = Arrays.asList("Files", "exec", "Runtime", "ProcessBuilder");
    private static final WordTree WORD_TREE;

    static {
        WORD_TREE = new WordTree();
        WORD_TREE.addWords(BLACK_LIST);
    }

    @Override
    protected boolean isCodeDangerous(String code) {
        return WORD_TREE.isMatch(code);
    }

    @Override
    protected String prepareWorkDirectory() {
         String globalCodePath = GLOBAL_CODE_PATH;
            if (StrUtil.isBlank(CommonConstant.WORKING_DIRECTORY)) {
                globalCodePath = "/sakura/sandbox/tmpCode";
            }
            String workDir = globalCodePath + File.separator + UUID.randomUUID();
            // 创建目录
            File dir = new File(workDir);
            dir.mkdirs();
            
            // 设置权限：所有用户可读写执行
            dir.setReadable(true, false); // 所有用户可读
            dir.setWritable(true, false); // 所有用户可写
            dir.setExecutable(true, false); // 所有用户可执行
            
            log.info("创建工作目录: {}, 权限已设置为全局可读写执行", workDir);
            return workDir;
    }

    @Override
    protected File saveCodeToFile(String code, String workDir) {
        String codePath = workDir + File.separator + CODE_FILE_NAME;
        log.info("准备工作目录: {}", workDir);
        File codeFile = FileUtil.writeString(code, codePath, StandardCharsets.UTF_8);
        
        // 设置文件权限：所有用户可读写
        codeFile.setReadable(true, false);
        codeFile.setWritable(true, false);
        codeFile.setExecutable(true, false);
        
        return codeFile;
    }

    @Override
    protected boolean needCompile() {
        return true; // Java需要编译
    }

    @Override
    protected void compileCode(File codeFiles, String workDir) throws Exception {
        if (codeFiles == null) {
            throw new RuntimeException("代码文件不存在");
        }

        // 构建编译命令
        String[] compileCommand = new String[COMPILE_CMD.length + 1];
        System.arraycopy(COMPILE_CMD, 0, compileCommand, 0, COMPILE_CMD.length);
        compileCommand[COMPILE_CMD.length] = codeFiles.getAbsolutePath();

        // 执行编译
        Process compileProcess = Runtime.getRuntime().exec(compileCommand);
        ExecuteMessage message = ProcessUtil.getExecuteMessage(compileProcess);

        if (JudgeConstant.NORMAL_EXIT_CODE != message.getExitCode()) {
            throw new RuntimeException(message.getErrorMessage());
        }

        log.info("Java代码编译成功: {}", codeFiles.getName());
    }

    /**
     * 创建并启动Docker容器
     */
    @Override
    protected String createAndStartContainer(String codeDirPath) {
        log.info("创建并启动Docker容器");
        log.info("codeDirPath: {}",  codeDirPath);
        log.info("SAND_BOX_CODE_DIR: {}",  CommonConstant.SAND_BOX_CODE_DIR);
        // 创建容器配置
        CreateContainerCmd containerCmd = dockerClient.createContainerCmd(DOCKER_IMAGE);
        HostConfig hostConfig = new HostConfig();

        // 设置资源限制
        hostConfig.withMemory(CommonConstant.MEMORY_LIMIT);
        hostConfig.withCpuCount(CommonConstant.SAND_BOX_CPU);
        hostConfig.withPrivileged(true);  // 添加特权模式
        hostConfig.withCapAdd(Capability.SYS_PTRACE);
        hostConfig.withBinds(new Bind(codeDirPath, new Volume(CommonConstant.SAND_BOX_CODE_DIR)));

        // 启用内存使用量统计
        hostConfig.withMemorySwappiness(0L);
        hostConfig.withOomKillDisable(true);

        // 创建容器
        String containerName = "java-sandbox-" + UUID.fastUUID();
        CreateContainerResponse createContainerResponse = containerCmd
                .withHostConfig(hostConfig)
                .withName(containerName)
                .withUser("root") // 指定使用root用户
                .withWorkingDir(CommonConstant.SAND_BOX_CODE_DIR)
                .withEnv("TERM=xterm")
                .withAttachStdin(true)
                .withAttachStderr(true)
                .withAttachStdout(true)
                .withTty(true)
                .withNetworkDisabled(true) // 禁用网络访问
                .exec();

        String containerId = createContainerResponse.getId();
        log.info("创建容器成功: id: {} name: {}", containerId, containerName);

        // 启动容器
        dockerClient.startContainerCmd(containerId).exec();

        return containerId;
    }

    /**
     * 在容器中执行代码
     */
    @Override
    protected ExecutionResult runCode(String containerId, String input) throws InterruptedException, IOException {
        // 准备输入
        String[] inputArgs = input.split(" ");
        String formattedInput = StrUtil.join("\r\n", (Object) inputArgs) + "\r\n";

        // 创建命令
        log.info("运行命令：{}", Arrays.toString(RUN_CMD));
        ExecCreateCmdResponse execCmd = dockerClient.execCreateCmd(containerId)
                .withCmd(RUN_CMD)
                .withAttachStdin(true)
                .withAttachStdout(true)
                .withAttachStderr(true)
                .exec();


        // 存储输出
        StringBuilder output = new StringBuilder();
        StringBuilder error = new StringBuilder();
        StopWatch stopWatch = new StopWatch();
        // 执行命令
        try (ByteArrayInputStream inputStream = new ByteArrayInputStream(formattedInput.getBytes())) {

            stopWatch.start();

            dockerClient.execStartCmd(execCmd.getId())
                    .withStdIn(inputStream)
                    .exec(new DockerExecuteCallback(output, error))
                    .awaitCompletion(CommonConstant.EXECUTE_TIMEOUT_SECONDS, TimeUnit.SECONDS);

            stopWatch.stop();

        }
        long executionTime = stopWatch.getLastTaskTimeMillis();
        // 获取内存使用情况 - 使用注入的实例方法
        long memoryUsageKb = dockerMemoryUtils.getMemoryUsageSingle(containerId);
        log.info("执行完成，内存使用: {}KB", memoryUsageKb);
        log.info("代码执行耗时: {}ms", executionTime);

        return new ExecutionResult(output.toString(), error.toString(), memoryUsageKb, executionTime);
    }

} 